//
//  DPUITester.m
//  UITestingKit
//
//  Created by Ofri Wolfus on 29/03/07.
//  Copyright 2007 Ofri Wolfus. All rights reserved.
//

#import "DPUITester.h"
#import "DPTestsModule.h"
#import "DPQualityAssurance-Private.h"
#import <ExceptionHandling/NSExceptionHandler.h>
#import "NSExceptionStackTrace.h"


@interface DPUITester (private)
- (BOOL)prepareNextUser;
@end

@implementation DPUITester

- (id)initWithModules:(NSArray *)m {
	if ((self = [super init])) {
		NSExceptionHandler *eh = [NSExceptionHandler defaultExceptionHandler];
		
		printStackTrace = NO;
		returnValue = 0;
		if ([modules count] == 0U)
			modules = [m mutableCopy];
		
		// Clean up the modules array from modules with no
		// user classes.
		NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet];
		unsigned i, count = [modules count];
		
		for (i = 0U; i < count; ++i)
			if (![[modules objectAtIndex:i] userClass])
				[indexes addIndex:i];
		
		[modules removeObjectsAtIndexes:indexes];
		
		[eh setDelegate:self];
		[eh setExceptionHandlingMask:(NSHandleUncaughtExceptionMask
									  //| NSLogUncaughtExceptionMask
									  | NSHandleUncaughtSystemExceptionMask
									  //| NSLogUncaughtSystemExceptionMask
									  | NSHandleUncaughtRuntimeErrorMask
									  //| NSLogUncaughtRuntimeErrorMask
									  //| NSLogTopLevelExceptionMask
									  | NSHandleTopLevelExceptionMask)];
		
		// Set up QA objects
		[DPQualityAssurance setUpTesters];
	}
	
	return self;
}

- (void)dealloc {
	[DPQualityAssurance tearDownTesters];
	[[NSExceptionHandler defaultExceptionHandler] setDelegate:nil];
	[modules release]; modules = nil;
	[super dealloc];
}

// Returns YES if a new user has been created
// and NO if there are no users left.
// This method also releases the current user.
- (BOOL)prepareNextUser {
	// Clean up the current user
	[currentUser release]; currentUser = nil;
	
	// Be sure we have more modules to work with
	if ([modules count] == 0U)
		return NO;
	
	// Create our new user object
	currentUser = [[[[modules objectAtIndex:0U] userClass] alloc] init];
	
	// We no longer need this module
	[modules removeObjectAtIndex:0U];
	
	return YES;
}

- (void)fire {
	// First, check our user's state (which, as a side effect,
	// makes it perform its next action).
	if (!currentUser || ![currentUser performNextAction]) {
		// We're done with this user.
		// Do we have another one or are we done?
		if (![self prepareNextUser]) {
			[self invalidate];
			return;
		}
	}
	
	/*
	 * In order to be sure all expected accessibility notifications
	 * have arrived, we signal ourself after a delay.
	 * If the user's action is expected to cause a large reaction this
	 * delay should be longer. Therefor, only the user can tell the delay
	 * it needs.
	 * If no delay is provided, we'll default to 0.5 sec.
	 *
	 * For some reason a timer will mess things up with the runloop(s)
	 * and cause us to crash, but as we must run in the default mode (or
	 * we won't receive accessibility notifications) we'll just start a nested
	 * runloop in the default mode.
	 */
	NSTimeInterval t = [currentUser respondsToSelector:@selector(actionsInterval)] ?
							[(NSObject *)currentUser actionsInterval] : 0.5;
	
	CFRunLoopRunInMode(kCFRunLoopDefaultMode,
					   t > 0.0 ? t : 0.5,	// Make sure we got a positive value
					   FALSE);
	
	// We're ready for a second round
	[self signal];
}

// This method gets called by NSExceptionHandler when an uncaught exception
// is thrown. This means something whent wrong so we'll invalidate ourself
// and let the exception handler do its job.
- (BOOL)exceptionHandler:(NSExceptionHandler *)sender
   shouldHandleException:(NSException *)exception
					mask:(unsigned int)aMask
{
	returnValue = -1;
	[self invalidate];
	
	NSString *format = @"UITestingKit has recorded the following exception:\n%@ -- %@";
	NSLog(printStackTrace ? [format stringByAppendingString:@"\n %@"] : format,
		  [exception name], [exception reason], [exception decodedStackTrace]);
	return YES;
}

- (BOOL)exceptionHandler:(NSExceptionHandler *)sender
	  shouldLogException:(NSException *)exception
					mask:(unsigned int)aMask {
	//[self invalidate];
	return NO;
}

- (void)setPrintStackTrace:(BOOL)flag {
	printStackTrace = flag;
}

- (int)returnValue {
	return returnValue;
}

@end
